import mx.utils.Delegate;

import gugga.sequence.TaskSequence;
import gugga.sequence.FictiveTask;
import gugga.sequence.ExecuteAsyncMethodTask;
import gugga.collections.LinkedList;
import gugga.collections.LinkedListItem;
import gugga.common.EventDescriptor;

import guggaLibTests.support.TaskExecutionEvaluator;
import guggaLibTests.support.PauseResumeMethodsSequence;

import test_framework.test.unit.TestCase;
import test_framework.app.exec.ForEachExecutable;
import gugga.common.ITask;
import gugga.utils.DebugUtils;
import gugga.debug.Debugger;
import gugga.sequence.ExecuteMethodTask;

/**
 * @author vladimir
 */
class guggaLibTests.TaskSequenceTest
		extends TestCase
{
	private var delayInterval:Number;
	
	public function TaskSequenceTest()
	{
		super();
		
		this.delayInterval = 1000;
	}
	
	public function testConstructor()
	{
		var taskSequence:TaskSequence = new TaskSequence();
		
		assertNotNull("taskSequence should not be null after creation", taskSequence);
		assertNotUndefined("taskSequence should not be undefined after creation", taskSequence);
	}
	
	public function testIsRunningBeforeStart()
	{
		var taskSequence:TaskSequence = new TaskSequence();
		assertFalse(
				"taskSequence has not started yet, so isRunning() should be false", 
				taskSequence.isRunning());
	}
	
	public function testIsRunningAfterStart()
	{
		// initialize TaskExecutionEvaluator:
		var evaluator:TaskExecutionEvaluator = new TaskExecutionEvaluator(this);
		evaluator.setDelayedEvaluation(this.delayInterval);
		
		var taskSequence:TaskSequence = new TaskSequence();
		taskSequence.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		taskSequence.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
		
		var fictiveTask:FictiveTask = new FictiveTask();
		var eventDescriptor:EventDescriptor = new EventDescriptor(
				fictiveTask, 
				"completed");
		var asyncTask:ExecuteAsyncMethodTask = ExecuteAsyncMethodTask.create(
				eventDescriptor, 
				this, 
				this.emptyMethod, 
				[]);
		asyncTask.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		asyncTask.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
		
		taskSequence.addTask(asyncTask);
		
		taskSequence.start();
		
		evaluator.registerRequiredTaskStatus({task:taskSequence, event:"start"});
		evaluator.registerRequiredTaskStatus({task:asyncTask, event:"start"});
		
		evaluator.pauseToEvaluateTaskExecution();
		
		assertTrue(
				"asyncTask has not completed yet, so taskSequence should still be in running state",
				taskSequence.isRunning());
		
	}
	
	private function emptyMethod()
	{
		// THIS METHOD SHOULD BE EMPTY!!!
	}
	
	public function testIsRunningAfterCompleted()
	{
		// initialize TaskExecutionEvaluator:
		var evaluator:TaskExecutionEvaluator = new TaskExecutionEvaluator(this);
		evaluator.setDelayedEvaluation(this.delayInterval);
		
		var taskSequence:TaskSequence = new TaskSequence();
		taskSequence.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		taskSequence.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
		
		var fictiveTask:FictiveTask = new FictiveTask();
		fictiveTask.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		fictiveTask.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
				
		taskSequence.addTask(fictiveTask);
		
		taskSequence.start();
		
		evaluator.registerRequiredTaskStatus({task:taskSequence, event:"start"});
		evaluator.registerRequiredTaskStatus({task:fictiveTask, event:"start"});
		evaluator.registerRequiredTaskStatus({task:fictiveTask, event:"completed"});
		evaluator.registerRequiredTaskStatus({task:taskSequence, event:"completed"});
		
		evaluator.pauseToEvaluateTaskExecution();
		
		assertFalse(
				"taskSequence has already finished, so isRunning() should return false", 
				taskSequence.isRunning());
	}
	
	public function testStartWithSingleTask()
	{
		// initialize TaskExecutionEvaluator:
		var evaluator:TaskExecutionEvaluator = new TaskExecutionEvaluator(this);
		evaluator.setDelayedEvaluation(this.delayInterval);
		
		var taskSequence:TaskSequence = new TaskSequence();
		taskSequence.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		taskSequence.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
		
		var fictiveTask:FictiveTask = new FictiveTask();
		fictiveTask.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		fictiveTask.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
				
		taskSequence.addTask(fictiveTask);
		
		taskSequence.start();
		
		evaluator.registerRequiredTaskStatus({task:taskSequence, event:"start"});
		evaluator.registerRequiredTaskStatus({task:fictiveTask, event:"start"});
		evaluator.registerRequiredTaskStatus({task:fictiveTask, event:"completed"});
		evaluator.registerRequiredTaskStatus({task:taskSequence, event:"completed"});
		
		evaluator.pauseToEvaluateTaskExecution();
	}
	
	public function testStartWithoutTasks()
	{
		// initialize TaskExecutionEvaluator:
		var evaluator:TaskExecutionEvaluator = new TaskExecutionEvaluator(this);
		evaluator.setDelayedEvaluation(this.delayInterval);
		
		var taskSequence:TaskSequence = new TaskSequence();
		taskSequence.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		taskSequence.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
		
		taskSequence.start();
		
		evaluator.registerRequiredTaskStatus({task:taskSequence, event:"start"});
		evaluator.registerRequiredTaskStatus({task:taskSequence, event:"completed"});
		
		evaluator.pauseToEvaluateTaskExecution();
	}
	
	private var evaluationContext:Object;
	
	public function testSequentialCompletion()
	{
		var testSequence:PauseResumeMethodsSequence 
			= new PauseResumeMethodsSequence(
					this, 
					this.delayInterval, 
					[startTestSequentialCompletion, 
					afterStartTestSequentialCompletion, 
					afterCompletingFirstTaskTestSequentialCompletion, 
					afterCompletingLastTaskTestSequentialCompletion, 
					afterCompletingTaskSequenceStartLastTask, 
					afterCompletingTaskSequenceTestCompletionAgain]);
		
		testSequence.start();
	}
	
	private function startTestSequentialCompletion() 
	{
		this.evaluationContext = null;
		this.evaluationContext = new Object();
		
		var evaluator:TaskExecutionEvaluator = new TaskExecutionEvaluator(this);
		this.evaluationContext["evaluator"] = evaluator;
		
		var taskSequence:TaskSequence = new TaskSequence();
		taskSequence.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		taskSequence.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
		
		this.evaluationContext["taskSequence"] = taskSequence;
		
		var fictiveEventSource1:FictiveTask = new FictiveTask();
		var eventDescriptor1:EventDescriptor = new EventDescriptor(
				fictiveEventSource1, 
				"completed");
		var asyncTask1:ExecuteAsyncMethodTask = ExecuteAsyncMethodTask.create(
				eventDescriptor1, 
				this, 
				this.emptyMethod, 
				[]);
		asyncTask1.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		asyncTask1.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
				
		this.evaluationContext["fictiveEventSource1"] = fictiveEventSource1;
		this.evaluationContext["asyncTask1"] = asyncTask1;
		
		var fictiveEventSource2:FictiveTask = new FictiveTask();
		var eventDescriptor2:EventDescriptor = new EventDescriptor(
				fictiveEventSource2, 
				"completed");
		var asyncTask2:ExecuteAsyncMethodTask = ExecuteAsyncMethodTask.create(
				eventDescriptor2, 
				this, 
				this.emptyMethod, 
				[]);
		asyncTask2.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		asyncTask2.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
		
		this.evaluationContext["fictiveEventSource2"] = fictiveEventSource2;
		this.evaluationContext["asyncTask2"] = asyncTask2;
		
		taskSequence.addTask(asyncTask1);
		taskSequence.addTask(asyncTask2);
		
		taskSequence.start();
		
		evaluator.registerRequiredTaskStatus({task:taskSequence, event:"start"});
		evaluator.registerRequiredTaskStatus({task:asyncTask1, event:"start"});
	}
	
	private function afterStartTestSequentialCompletion()
	{
		var evaluator:TaskExecutionEvaluator 
			= TaskExecutionEvaluator(this.evaluationContext["evaluator"]);
		
		evaluator.evaluateTaskExecution();
		
		var fictiveEventSource1:FictiveTask 
			= FictiveTask(this.evaluationContext["fictiveEventSource1"]);
		var asyncTask1:ExecuteAsyncMethodTask 
			= ExecuteAsyncMethodTask(this.evaluationContext["asyncTask1"]);
		var asyncTask2:ExecuteAsyncMethodTask 
			= ExecuteAsyncMethodTask(this.evaluationContext["asyncTask2"]);
		
		// complete asyncTask1:
		fictiveEventSource1.start();
		
		evaluator.registerRequiredTaskStatus({task:asyncTask1, event:"completed"});
		evaluator.registerRequiredTaskStatus({task:asyncTask2, event:"start"});
	}
	
	private function afterCompletingFirstTaskTestSequentialCompletion()
	{
		var evaluator:TaskExecutionEvaluator 
			= TaskExecutionEvaluator(this.evaluationContext["evaluator"]);
		
		evaluator.evaluateTaskExecution();
		
		var fictiveEventSource2:FictiveTask 
			= FictiveTask(this.evaluationContext["fictiveEventSource2"]);
		var asyncTask2:ExecuteAsyncMethodTask 
			= ExecuteAsyncMethodTask(this.evaluationContext["asyncTask2"]);
		
		var taskSequence:TaskSequence 
			= TaskSequence(this.evaluationContext["taskSequence"]);
		
		// complete asyncTask2:
		fictiveEventSource2.start();
		
		evaluator.registerRequiredTaskStatus({task:asyncTask2, event:"completed"});
		evaluator.registerRequiredTaskStatus({task:taskSequence, event:"completed"});
	}
	
	private function afterCompletingLastTaskTestSequentialCompletion()
	{
		var evaluator:TaskExecutionEvaluator 
			= TaskExecutionEvaluator(this.evaluationContext["evaluator"]);
		
		evaluator.evaluateTaskExecution();
	}
	
	private function afterCompletingTaskSequenceStartLastTask()
	{
		var evaluator:TaskExecutionEvaluator 
			= TaskExecutionEvaluator(this.evaluationContext["evaluator"]);
		
		var fictiveEventSource2:FictiveTask 
			= FictiveTask(this.evaluationContext["fictiveEventSource2"]);
		var asyncTask2:ExecuteAsyncMethodTask 
			= ExecuteAsyncMethodTask(this.evaluationContext["asyncTask2"]);
		
		var taskSequence:TaskSequence 
			= TaskSequence(this.evaluationContext["taskSequence"]);
		
		// start&complete asyncTask2 again:
		asyncTask2.start();
		fictiveEventSource2.start();
		
		evaluator.registerRequiredTaskStatus({task:asyncTask2, event:"start"});
		evaluator.registerRequiredTaskStatus({task:asyncTask2, event:"completed"});
	}
	
	private function afterCompletingTaskSequenceTestCompletionAgain()
	{
		var evaluator:TaskExecutionEvaluator 
			= TaskExecutionEvaluator(this.evaluationContext["evaluator"]);
		
		evaluator.evaluateTaskExecution();
		evaluator.assertTaskStatusRegisterEqualsRequiredTaskStatusRegister("No more completed events from the taskSequence should be caught!");
	}
	
	public function testAddTaskWhenNotStarted()
	{
		var evaluator:TaskExecutionEvaluator = new TaskExecutionEvaluator(this);
		
		var taskSequence:TaskSequence = new TaskSequence();
		taskSequence.addEventListener(
				"taskAdded", 
				Delegate.create(evaluator, evaluator.onTaskAdded));
		
		var fictiveTask1:FictiveTask = new FictiveTask();
		taskSequence.addTask(fictiveTask1);
		
		var tasks:Array = taskSequence.getAllTasks();
		assertEquals(
				"taskSequence should contain only 1 task after single addTask()", 
				1, 
				tasks.length);
		assertInstanceOf(
				"these tasks should be the same", 
				tasks[0], 
				FictiveTask);
		
		var fictiveTask2:FictiveTask = new FictiveTask();
		taskSequence.addTask(fictiveTask2);
		
		tasks = taskSequence.getAllTasks();
		assertEquals(
				"taskSequence should contain only 2 tasks after this addTask()", 
				2, 
				tasks.length);
		assertInstanceOf(
				"these tasks should be the same", 
				tasks[0], 
				FictiveTask);
		assertInstanceOf(
				"these tasks should be the same", 
				tasks[1], 
				FictiveTask);
		
		// try to add already added task:
		assertThrows(
				"This task already exists in the sequence", 
				taskSequence, 
				taskSequence.addTask, 
				[fictiveTask2]);
		assertEquals(
				"taskSequence should contain only 2 tasks after trying to add already added task", 
				2, 
				tasks.length);
		assertInstanceOf(
				"these tasks should be the same", 
				tasks[0], 
				FictiveTask);
		assertInstanceOf(
				"these tasks should be the same", 
				tasks[1], 
				FictiveTask);
		
		var initializeTestAddTask:Function = function()
		{
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"taskAdded"});
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"taskAdded"});
		};
		
		var assertRaisedTaskAddedEvents:Function = function()
		{
			evaluator.evaluateTaskExecution();
			evaluator.assertTaskStatusRegisterEqualsRequiredTaskStatusRegister("two taskAdded events should be caught and consumed");
		};
		
		var testSequence:PauseResumeMethodsSequence 
			= new PauseResumeMethodsSequence(
					this, 
					this.delayInterval, 
					[initializeTestAddTask, 
					assertRaisedTaskAddedEvents]);
		
		testSequence.start();
	}
	
	public function testAddTaskAfterCompleted()
	{
		var evaluator:TaskExecutionEvaluator = new TaskExecutionEvaluator(this);
		
		var fictiveTask1:FictiveTask = new FictiveTask();
		fictiveTask1.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		fictiveTask1.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
				
		var taskSequence:TaskSequence = new TaskSequence();
		taskSequence.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		taskSequence.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
				
		taskSequence.addTask(fictiveTask1);
		
		var startAndCompleteTaskSequence:Function = function()
		{
			taskSequence.start();
			
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"start"});
			evaluator.registerRequiredTaskStatus({task:fictiveTask1, event:"start"});
			evaluator.registerRequiredTaskStatus({task:fictiveTask1, event:"completed"});
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"completed"});
		};
		
		var fictiveTask2:FictiveTask = new FictiveTask();
		
		var assertCompletedAndTestAddTask:Function = function()
		{
			evaluator.evaluateTaskExecution();
			
			taskSequence.addTask(fictiveTask2);
			
			var tasks:Array = taskSequence.getAllTasks();
			this.assertEquals(
					"taskSequence should contain 2 tasks after this addTask()", 
					2, 
					tasks.length);
			this.assertInstanceOf(
					"these tasks should be the same", 
					tasks[0], 
					FictiveTask);
			this.assertInstanceOf(
					"these tasks should be the same", 
					tasks[1], 
					FictiveTask);
		};
		
		var testSequence:PauseResumeMethodsSequence 
			= new PauseResumeMethodsSequence(
					this, 
					this.delayInterval, 
					[startAndCompleteTaskSequence, 
					assertCompletedAndTestAddTask]);
		
		testSequence.start();
	}
	
	public function testAddTaskWhenRunning()
	{
		var evaluator:TaskExecutionEvaluator = new TaskExecutionEvaluator(this);
		
		var fictiveEventSource:FictiveTask = new FictiveTask();
		var eventDescriptor:EventDescriptor = new EventDescriptor(
				fictiveEventSource, 
				"completed");
		var asyncTask:ExecuteAsyncMethodTask = ExecuteAsyncMethodTask.create(
				eventDescriptor, 
				this, 
				this.emptyMethod, 
				[]);
		asyncTask.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		asyncTask.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
		
		var taskSequence:TaskSequence = new TaskSequence();
		taskSequence.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		taskSequence.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
				
		taskSequence.addTask(asyncTask);
		
		var startTaskSequence:Function = function()
		{
			taskSequence.start();
			
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"start"});
			evaluator.registerRequiredTaskStatus({task:asyncTask, event:"start"});
		};
		
		var fictiveTask:FictiveTask = new FictiveTask();
		
		var assertIsRunningAndAddTask:Function = function()
		{
			evaluator.evaluateTaskExecution();
			evaluator.assertTaskStatusRegisterEqualsRequiredTaskStatusRegister("taskSequence should only be started - asyncTask is still not completed");
			
			this.assertTrue(
					"taskSequence should be in running state now",
					taskSequence.isRunning());
			
			taskSequence.addTask(fictiveTask);
			
			var tasks:Array = taskSequence.getAllTasks();
			this.assertEquals(
					"taskSequence should contain 2 tasks after this addTask()", 
					2, 
					tasks.length);
			this.assertInstanceOf(
					"these tasks should be the same", 
					tasks[0], 
					ExecuteAsyncMethodTask);
			this.assertInstanceOf(
					"these tasks should be the same", 
					tasks[1], 
					FictiveTask);
		};
		
		var testSequence:PauseResumeMethodsSequence 
			= new PauseResumeMethodsSequence(
					this, 
					this.delayInterval, 
					[startTaskSequence, 
					assertIsRunningAndAddTask]);
		
		testSequence.start();
	}
	
	public function testAddTaskAfter()
	{
		var sequence : TaskSequence = new TaskSequence();
		
		var task1 = new FictiveTask();
		var task2 = new FictiveTask();
		var newTask = new FictiveTask();
		
		sequence.addTask(task1);
		sequence.addTask(task2);
		
		sequence.addTaskAfter(task1, newTask);
		
		var tasks : Array = sequence.getAllTasks();
		
		for(var i:Number = 0; i < tasks.length; i++)
		{
			if(tasks[i] === task1)
			{
				assertEquals(
					"these tasks should be equal", 
					tasks[i+1], 
					newTask);
			}
		}
	}
	
	public function testAddTaskAfterNonExistentTask ()
	{
		var sequence : TaskSequence = new TaskSequence();
		
		var task1 = new FictiveTask();
		var task2 = new FictiveTask();
		var task3 = new FictiveTask();
		
		var nonExistentTask = new FictiveTask();
		
		sequence.addTask(task1);
		sequence.addTask(task2);
		
		sequence.addTaskAfter(nonExistentTask, task3);
		
		var tasks : Array = sequence.getAllTasks();
		
		var assertionBool : Boolean = true; 
		
		for(var i:Number = 0; i < tasks.length; i++)
		{
			if(tasks[i] === task3)
			{
				assertionBool = false;
			}
		}
		
		assertTrue(
			"this task should't been added after nonexistent one, but it is",
			assertionBool
		);
	} 
	
	public function testAddTaskBefore()
	{
		var sequence : TaskSequence = new TaskSequence();
		
		var task1 = new FictiveTask();
		var task2 = new FictiveTask();
		var newTask = new FictiveTask();
		
		sequence.addTask(task1);
		sequence.addTask(task2);
		
		sequence.addTaskBefore(task2, newTask);
		
		var tasks : Array = sequence.getAllTasks();
		
		for(var i:Number = 0; i < tasks.length; i++)
		{
			if(tasks[i] === task2)
			{
				assertEquals(
					"these tasks should be equal", 
					tasks[i-1], 
					newTask);
			}
		}
	}
	
	public function testAddTaskBeforeNonExistentTask ()
	{
		var sequence : TaskSequence = new TaskSequence();
		
		var task1 = new FictiveTask();
		var task2 = new FictiveTask();
		var task3 = new FictiveTask();
		
		var nonExistentTask = new FictiveTask();
		
		sequence.addTask(task1);
		sequence.addTask(task2);
		
		sequence.addTaskBefore(nonExistentTask, task3);
		
		var tasks : Array = sequence.getAllTasks();
		
		var assertionBool : Boolean = true; 
		
		for(var i:Number = 0; i < tasks.length; i++)
		{
			if(tasks[i] === task3)
			{
				assertionBool = false;
			}
		}
		
		assertTrue(
			"this task should't been added before nonexistent one, but it is",
			assertionBool
		);
	} 
	
	public function testGetTaskAfter () 
	{
		var sequence : TaskSequence = new TaskSequence();
		
		var task1 : ITask = new FictiveTask();
		var task2 : ITask = new FictiveTask();
		
		sequence.addTask(task1);
		sequence.addTask(task2);
		
		var newTask : ITask;
		
		newTask = sequence.getTaskAfter(task1);
		
		assertEquals(
			"these tasks should be equal",
			newTask,
			task2
		);
	}
	
	public function testGetTaskAfterNonExistentOne () 
	{
		var sequence : TaskSequence = new TaskSequence();
		
		var task1 : ITask = new FictiveTask();
		var task2 : ITask = new FictiveTask();
		
		sequence.addTask(task1);
		sequence.addTask(task2);
		
		var newTask : ITask;
		var nonExistentTask : ITask = new FictiveTask();
		
		newTask = sequence.getTaskAfter(nonExistentTask);
		
		assertUndefined(
			"expected result should be undefined",
			newTask
		);
	}

	public function testGetTaskBefore () 
	{
		var sequence : TaskSequence = new TaskSequence();
		
		var task1 : ITask = new FictiveTask();
		var task2 : ITask = new FictiveTask();
		
		sequence.addTask(task1);
		sequence.addTask(task2);
		
		var newTask : ITask;
		
		newTask = sequence.getTaskBefore(task2);
		
		assertEquals(
			"these tasks should be equal",
			task1,
			newTask
		);
	}
	
	public function testGetTaskBeforeNonExistentOne () 
	{
		var sequence : TaskSequence = new TaskSequence();
		
		var task1 : ITask = new FictiveTask();
		var task2 : ITask = new FictiveTask();
		
		sequence.addTask(task1);
		sequence.addTask(task2);
		
		var newTask : ITask;
		var nonExistentTask : ITask = new FictiveTask();
		
		newTask = sequence.getTaskBefore(nonExistentTask);
		
		assertUndefined(
			"expected result should be undefined",
			newTask
		);
	}
	
	public function testNestedTaskSequenceCompletion () 
	{
		var evaluator : TaskExecutionEvaluator = new TaskExecutionEvaluator(this);
		var sequence : TaskSequence = new TaskSequence();
			
		var taskToCompleteAsync1 : ITask = new FictiveTask();
		var taskToCompleteAsync2 : ITask = new FictiveTask();
		
		var task1CompletedEventDescription : EventDescriptor = new EventDescriptor(
			taskToCompleteAsync1,
			"completed"
		);
		
		var task2CompletedEventDescription : EventDescriptor = new EventDescriptor(
			taskToCompleteAsync2,
			"completed"
		);
		
		var asyncTask1 : ITask = ExecuteAsyncMethodTask.create(
			task1CompletedEventDescription,
			this,
			this.emptyMethod,
			[]
		);
		
		var asyncTask2 : ITask = ExecuteAsyncMethodTask.create(
			task2CompletedEventDescription,
			this,
			this.emptyMethod,
			[]
		);
		
		var sequenceTask : TaskSequence = new TaskSequence();
		
		var taskToCompleteAsync3 : ITask = new FictiveTask();
		var task3CompletedEventDescription : EventDescriptor = new EventDescriptor(
			taskToCompleteAsync3,
			"completed"
		);
		
		var taskToCompleteAsync4 : ITask = new FictiveTask();
		var task4CompletedEventDescriptor : EventDescriptor = new EventDescriptor(
			taskToCompleteAsync4,
			"completed"
		);
		
		var asyncTask3 : ITask = ExecuteAsyncMethodTask.create(
			task3CompletedEventDescription,
			this,
			this.emptyMethod,
			null
		);
		
		var asyncTask4 : ITask = ExecuteAsyncMethodTask.create(
			task4CompletedEventDescriptor,
			this,
			this.emptyMethod,
			null
		);
			
		sequenceTask.addTask(asyncTask3);
		sequenceTask.addTask(asyncTask4);
			
		var startTest = function () 
		{
			sequence.addEventListener("start",Delegate.create(evaluator,evaluator.onTaskStarted));
			sequence.addEventListener("completed", Delegate.create(evaluator, evaluator.onTaskCompleted));
			
			sequenceTask.addEventListener("start",Delegate.create(evaluator,evaluator.onTaskStarted));
			sequenceTask.addEventListener("completed", Delegate.create(evaluator, evaluator.onTaskCompleted));
			
			asyncTask1.addEventListener("start", Delegate.create(evaluator, evaluator.onTaskStarted));
			asyncTask1.addEventListener("completed", Delegate.create(evaluator, evaluator.onTaskCompleted));
			
			asyncTask2.addEventListener("start", Delegate.create(evaluator, evaluator.onTaskStarted));
			asyncTask2.addEventListener("completed", Delegate.create(evaluator, evaluator.onTaskCompleted));
			
			asyncTask3.addEventListener("start", Delegate.create(evaluator, evaluator.onTaskStarted));
			asyncTask3.addEventListener("completed", Delegate.create(evaluator, evaluator.onTaskCompleted));
			
			asyncTask4.addEventListener("start", Delegate.create(evaluator, evaluator.onTaskStarted));
			asyncTask4.addEventListener("completed", Delegate.create(evaluator, evaluator.onTaskCompleted));
			
			sequence.addTask(asyncTask1);
			sequence.addTask(sequenceTask);
			sequence.addTask(asyncTask2);
			
			sequence.start();
			
			evaluator.registerRequiredTaskStatus( {task: sequence, event: "start"});
			evaluator.registerRequiredTaskStatus( {task: asyncTask1, event: "start"});				
		};
		
		var afterStartTask = function ()
		{
			evaluator.evaluateTaskExecution();
	
			taskToCompleteAsync1.start();
	
			evaluator.registerRequiredTaskStatus( { task: asyncTask1, event: "completed" }	);
			evaluator.registerRequiredTaskStatus( { task: sequenceTask, event: "start"} );
			evaluator.registerRequiredTaskStatus( { task: asyncTask3, event: "start"} );		
		};
		
		var secondTask = function () 
		{
			evaluator.evaluateTaskExecution();

			taskToCompleteAsync3.start();			
			
			evaluator.registerRequiredTaskStatus( { task: asyncTask3, event: "completed"} );
			evaluator.registerRequiredTaskStatus( { task: asyncTask4, event: "start"} );
		};
		
		var afterSecondTask = function ()
		{
			evaluator.evaluateTaskExecution();
			
			taskToCompleteAsync4.start();
			
			evaluator.registerRequiredTaskStatus( { task: asyncTask4, event: "completed"} );
			evaluator.registerRequiredTaskStatus( { task: sequenceTask, event: "completed"} );						
			evaluator.registerRequiredTaskStatus( { task: asyncTask2, event: "start"} );						
		};
		
		var fourthTask = function () 
		{
			evaluator.evaluateTaskExecution();
			
			taskToCompleteAsync2.start();			
			
			evaluator.registerRequiredTaskStatus( { task: asyncTask2, event: "completed"} );	
			evaluator.registerRequiredTaskStatus( { task: sequence, event: "completed"} );
		};
		
		var fifthTask = function () 
		{
			evaluator.evaluateTaskExecution();
		};
		
		var testSequence : PauseResumeMethodsSequence = new PauseResumeMethodsSequence (
			this,
			this.delayInterval,
			[startTest,
			afterStartTask,
			secondTask,
			afterSecondTask,
			fourthTask,
			fifthTask]
		);
		
		testSequence.start();
	}
	
	public function testRemoveTaskWhenNotRunning()
	{
		var evaluator:TaskExecutionEvaluator = new TaskExecutionEvaluator(this);
		
		var taskSequence:TaskSequence = new TaskSequence();
		taskSequence.addEventListener(
				"taskAdded", 
				Delegate.create(evaluator, evaluator.onTaskAdded));
		taskSequence.addEventListener(
				"taskRemoved", 
				Delegate.create(evaluator, evaluator.onTaskRemoved));
		
		var fictiveTask1:FictiveTask = new FictiveTask();
		taskSequence.addTask(fictiveTask1);
		
		var fictiveTask2:FictiveTask = new FictiveTask();
		taskSequence.addTask(fictiveTask2);
		
		var initializeTest:Function = function()
		{
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"taskAdded"});
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"taskAdded"});
		};
		
		var assertAddedTasksAndRemoveFirstTask:Function = function()
		{
			evaluator.evaluateTaskExecution();
			evaluator.assertTaskStatusRegisterEqualsRequiredTaskStatusRegister("two taskAdded events should be caught and consumed");
			
			taskSequence.removeTask(fictiveTask1);
			
			var tasks:Array = taskSequence.getAllTasks();
			this.assertEquals(
					"taskSequence should contain only 1 tasks after this removeTask()", 
					1, 
					tasks.length);
			
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"taskRemoved"});
		};
		
		var assertFirstTaskRemovedAndRemoveSecondTask:Function = function()
		{
			evaluator.evaluateTaskExecution();
			evaluator.assertTaskStatusRegisterEqualsRequiredTaskStatusRegister("two taskAdded and one taskRemoved events should be caught and consumed");
			
			taskSequence.removeTask(fictiveTask2);
			
			var tasks:Array = taskSequence.getAllTasks();
			this.assertEquals(
					"taskSequence should contain no tasks after this removeTask()", 
					0, 
					tasks.length);
			
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"taskRemoved"});
		};
		
		var assertSecondTaskRemoved:Function = function()
		{	
			evaluator.evaluateTaskExecution();
			evaluator.assertTaskStatusRegisterEqualsRequiredTaskStatusRegister("two taskAdded and two taskRemoved events should be caught and consumed");
		};
		
		var testSequence:PauseResumeMethodsSequence 
			= new PauseResumeMethodsSequence(
					this, 
					this.delayInterval, 
					[initializeTest, 
					assertAddedTasksAndRemoveFirstTask, 
					assertFirstTaskRemovedAndRemoveSecondTask, 
					assertSecondTaskRemoved]);
		
		testSequence.start();
	}
	
	public function testRemoveRunningTask()
	{
		var evaluator:TaskExecutionEvaluator = new TaskExecutionEvaluator(this);
		
		var fictiveEventSource:FictiveTask = new FictiveTask();
		var eventDescriptor:EventDescriptor = new EventDescriptor(
				fictiveEventSource, 
				"completed");
		var asyncTask:ExecuteAsyncMethodTask = ExecuteAsyncMethodTask.create(
				eventDescriptor, 
				this, 
				this.emptyMethod, 
				[]);
		asyncTask.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		
		var taskSequence:TaskSequence = new TaskSequence();
		taskSequence.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		taskSequence.addEventListener(
				"taskRemoved", 
				Delegate.create(evaluator, evaluator.onTaskRemoved));
				
		taskSequence.addTask(asyncTask);
		
		var startTaskSequence:Function = function()
		{
			taskSequence.start();
			
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"start"});
			evaluator.registerRequiredTaskStatus({task:asyncTask, event:"start"});
		};
		
		var assertIsRunningAndTestRemoveRunningTask:Function = function()
		{
			evaluator.evaluateTaskExecution();
			this.assertTrue(
					"taskSequence should be started and running now", 
					taskSequence.isRunning());
			
			// try to remove the running task:
			this.assertThrows(
					"Can not delete currently running task", 
					taskSequence, 
					taskSequence.removeTask, 
					[asyncTask]);
			
			var tasks:Array = taskSequence.getAllTasks();
			this.assertEquals(
					"taskSequence should still contain asyncTask after this removeTask(asyncTask) - asyncTask is still running", 
					1, 
					tasks.length);
		};
		
		var testSequence:PauseResumeMethodsSequence 
			= new PauseResumeMethodsSequence(
					this, 
					this.delayInterval, 
					[startTaskSequence, 
					assertIsRunningAndTestRemoveRunningTask]);
		
		testSequence.start();
	}
	
	public function testInterruptBeforeStart()
	{
		var evaluator:TaskExecutionEvaluator = new TaskExecutionEvaluator(this);
		
		var fictiveTask:FictiveTask = new FictiveTask();
		fictiveTask.addEventListener(
				"interrupted", 
				Delegate.create(evaluator, evaluator.onTaskInterrupted));
		
		var taskSequence:TaskSequence = new TaskSequence();
		taskSequence.addEventListener(
				"interrupted", 
				Delegate.create(evaluator, evaluator.onTaskInterrupted));
		
		taskSequence.addTask(fictiveTask);
		
		var interruptTaskSequence:Function = function()
		{
			taskSequence.interrupt();
			
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"interrupted"});
		};
		
		var assertOnlyTaskSequenceInterrupted:Function = function()
		{
			evaluator.evaluateTaskExecution();
			evaluator.assertTaskStatusRegisterEqualsRequiredTaskStatusRegister("only taskSequence should be interrupted");
		};
		
		var testSequence:PauseResumeMethodsSequence 
			= new PauseResumeMethodsSequence(
					this, 
					this.delayInterval, 
					[interruptTaskSequence, 
					assertOnlyTaskSequenceInterrupted]);
		
		testSequence.start();
	}
	
	public function testInterruptAfterCompleted()
	{
		var evaluator:TaskExecutionEvaluator = new TaskExecutionEvaluator(this);
		
		var fictiveTask:FictiveTask = new FictiveTask();
		fictiveTask.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		fictiveTask.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
		fictiveTask.addEventListener(
				"interrupted", 
				Delegate.create(evaluator, evaluator.onTaskInterrupted));
		
		var taskSequence:TaskSequence = new TaskSequence();
		taskSequence.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		taskSequence.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
		taskSequence.addEventListener(
				"interrupted", 
				Delegate.create(evaluator, evaluator.onTaskInterrupted));
		
		taskSequence.addTask(fictiveTask);
		
		var startAndCompleteTaskSequence:Function = function()
		{
			taskSequence.start();
			
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"start"});
			evaluator.registerRequiredTaskStatus({task:fictiveTask, event:"start"});
			evaluator.registerRequiredTaskStatus({task:fictiveTask, event:"completed"});
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"completed"});
		};
		
		var assertCompletedAndInterrupt:Function = function()
		{
			evaluator.evaluateTaskExecution();
			
			taskSequence.interrupt();
			
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"interrupted"});
		};
		
		var assertOnlyTaskSequenceInterrupted:Function = function()
		{
			evaluator.evaluateTaskExecution();
			evaluator.assertTaskStatusRegisterEqualsRequiredTaskStatusRegister("only taskSequence should be interrupted");
		};
		
		var testSequence:PauseResumeMethodsSequence 
			= new PauseResumeMethodsSequence(
					this, 
					this.delayInterval, 
					[startAndCompleteTaskSequence,
					assertCompletedAndInterrupt,
					assertOnlyTaskSequenceInterrupted]);
		
		testSequence.start();
	}
	
	public function testInterruptWhenRunning()
	{
		var evaluator:TaskExecutionEvaluator = new TaskExecutionEvaluator(this);
		
		var fictiveEventSource:FictiveTask = new FictiveTask();
		var eventDescriptor:EventDescriptor = new EventDescriptor(
				fictiveEventSource, 
				"completed");
		var asyncTask:ExecuteAsyncMethodTask = ExecuteAsyncMethodTask.create(
				eventDescriptor, 
				this, 
				this.emptyMethod, 
				[]);
		asyncTask.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		asyncTask.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
		asyncTask.addEventListener(
				"interrupted", 
				Delegate.create(evaluator, evaluator.onTaskInterrupted));
		
		var taskSequence:TaskSequence = new TaskSequence();
		taskSequence.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		taskSequence.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
		taskSequence.addEventListener(
				"interrupted", 
				Delegate.create(evaluator, evaluator.onTaskInterrupted));
				
		taskSequence.addTask(asyncTask);
		
		var startTaskSequence:Function = function()
		{
			taskSequence.start();
			
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"start"});
			evaluator.registerRequiredTaskStatus({task:asyncTask, event:"start"});
		};
		
		var assertTaskSequenceIsRunningAndInterrupt:Function = function()
		{
			evaluator.evaluateTaskExecution();
			this.assertTrue(
				"taskSequence should be in running state now", 
				taskSequence.isRunning());
				
			taskSequence.interrupt();
			// this is needed in order to interrupt asyncTask:
			fictiveEventSource.start();
			
			evaluator.registerRequiredTaskStatus({task:asyncTask, event:"interrupted"});
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"interrupted"});
		};
		
		var assertTaskSequenceInterrupted:Function = function()
		{
			evaluator.evaluateTaskExecution();
		};
		
		var testSequence:PauseResumeMethodsSequence 
			= new PauseResumeMethodsSequence(
					this, 
					this.delayInterval, 
					[startTaskSequence, 
					assertTaskSequenceIsRunningAndInterrupt, 
					assertTaskSequenceInterrupted]);
		
		testSequence.start();
	}
	
	public function testIsImmediatelyInterruptableBeforeStart()
	{
		var taskSequence:TaskSequence = new TaskSequence();
		
		assertTrue(
				"taskSequence should be immediately interruptable before start()", 
				taskSequence.isImmediatelyInterruptable());
	}
	
	public function testIsImmediatelyInterruptableAfterCompleted()
	{
		var evaluator:TaskExecutionEvaluator = new TaskExecutionEvaluator(this);
		
		var fictiveTask:FictiveTask = new FictiveTask();
		fictiveTask.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		fictiveTask.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
		
		var taskSequence:TaskSequence = new TaskSequence();
		taskSequence.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		taskSequence.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
		
		taskSequence.addTask(fictiveTask);
		
		var startAndCompleteTaskSequence:Function = function()
		{
			taskSequence.start();
			
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"start"});
			evaluator.registerRequiredTaskStatus({task:fictiveTask, event:"start"});
			evaluator.registerRequiredTaskStatus({task:fictiveTask, event:"completed"});
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"completed"});
		};
		
		var assertCompletedAndTestIsImmediatelyInterruptable:Function = function()
		{
			evaluator.evaluateTaskExecution();
			
			this.assertTrue(
					"taskSequence should be immediately interruptable after completed", 
					taskSequence.isImmediatelyInterruptable());
		};
		
		var testSequence:PauseResumeMethodsSequence 
			= new PauseResumeMethodsSequence(
					this, 
					this.delayInterval, 
					[startAndCompleteTaskSequence, 
					assertCompletedAndTestIsImmediatelyInterruptable]);
		
		testSequence.start(); 
	}
	
	public function testIsImmediatelyInterruptableWhenRunningAsyngTask()
	{
		var evaluator:TaskExecutionEvaluator = new TaskExecutionEvaluator(this);
		
		var fictiveEventSource:FictiveTask = new FictiveTask();
		var eventDescriptor:EventDescriptor = new EventDescriptor(
				fictiveEventSource, 
				"completed");
		var asyncTask:ExecuteAsyncMethodTask = ExecuteAsyncMethodTask.create(
				eventDescriptor, 
				this, 
				this.emptyMethod, 
				[]);
		asyncTask.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		asyncTask.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
		
		var taskSequence:TaskSequence = new TaskSequence();
		taskSequence.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		taskSequence.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
				
		taskSequence.addTask(asyncTask);
		
		var startTaskSequence:Function = function()
		{
			taskSequence.start();
			
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"start"});
			evaluator.registerRequiredTaskStatus({task:asyncTask, event:"start"});
		};
		
		var assertTaskSequenceIsRunningAndTestIsImmediatelyInterruptable:Function = function()
		{
			evaluator.evaluateTaskExecution();
			this.assertTrue(
					"taskSequence should be in running state now", 
					taskSequence.isRunning());
			
			this.assertEquals(
					"taskSequence.isImmediatelyInterruptable() should equals now asyncTask.isImmediatelyInterruptable()", 
					taskSequence.isImmediatelyInterruptable(), 
					asyncTask.isImmediatelyInterruptable());
		};
		
		var testSequence:PauseResumeMethodsSequence 
			= new PauseResumeMethodsSequence(
					this, 
					this.delayInterval, 
					[startTaskSequence, 
					assertTaskSequenceIsRunningAndTestIsImmediatelyInterruptable]);
		
		testSequence.start();
	}
	
	public function testIsImmediatelyInterruptableAfterInterrupt()
	{
		var evaluator:TaskExecutionEvaluator = new TaskExecutionEvaluator(this);
		
		var fictiveEventSource:FictiveTask = new FictiveTask();
		var eventDescriptor:EventDescriptor = new EventDescriptor(
				fictiveEventSource, 
				"completed");
		var asyncTask:ExecuteAsyncMethodTask = ExecuteAsyncMethodTask.create(
				eventDescriptor, 
				this, 
				this.emptyMethod, 
				[]);
		asyncTask.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		asyncTask.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
		asyncTask.addEventListener(
				"interrupted", 
				Delegate.create(evaluator, evaluator.onTaskInterrupted));
		
		var taskSequence:TaskSequence = new TaskSequence();
		taskSequence.addEventListener(
				"start", 
				Delegate.create(evaluator, evaluator.onTaskStarted));
		taskSequence.addEventListener(
				"completed", 
				Delegate.create(evaluator, evaluator.onTaskCompleted));
		taskSequence.addEventListener(
				"interrupted", 
				Delegate.create(evaluator, evaluator.onTaskInterrupted));
				
		taskSequence.addTask(asyncTask);
		
		var startTaskSequence:Function = function()
		{
			taskSequence.start();
			
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"start"});
			evaluator.registerRequiredTaskStatus({task:asyncTask, event:"start"});
		};
		
		var assertIsRunningAndInterrupt:Function = function()
		{
			evaluator.evaluateTaskExecution();
			this.assertTrue(
				"taskSequence should be in running state now", 
				taskSequence.isRunning());
				
			taskSequence.interrupt();
			// this is needed in order to interrupt asyncTask:
			fictiveEventSource.start();
			
			evaluator.registerRequiredTaskStatus({task:asyncTask, event:"interrupted"});
			evaluator.registerRequiredTaskStatus({task:taskSequence, event:"interrupted"});
		};
		
		var assertInterruptedAndTestIsImmediatelyInterruptable:Function = function()
		{
			evaluator.evaluateTaskExecution();
			
			this.assertTrue(
					"taskSequence should be immediately interruptable after interrupt()", 
					taskSequence.isImmediatelyInterruptable());
		};
		
		var testSequence:PauseResumeMethodsSequence 
			= new PauseResumeMethodsSequence(
					this, 
					this.delayInterval, 
					[startTaskSequence, 
					assertIsRunningAndInterrupt, 
					assertInterruptedAndTestIsImmediatelyInterruptable]);
		
		testSequence.start();
	}
	
	public function testMarkTask ()
	{
		var sequence : TaskSequence = new TaskSequence();
		var task1 : ITask = new FictiveTask();
		var task2 : ITask = new FictiveTask();
		
		sequence.addTask(task1);
		sequence.addTask(task2);
		
		sequence.markTask(task2, "final");
		
		assertNotUndefined(
			"getMarkedTask(marker) should not be undefined",
			sequence.getMarkedTask("final")
		);
		
		assertEquals(
			"getMarkedTask(marker) should match the marked task ",
			sequence.getMarkedTask("final"),
			task2
		);
	}
}